// TODO: JS是什么
// JS：是一种运行在客户端（浏览器）上的 “编程语言” ，实现人机交互效果


// TODO: JS注释
// 单行注释：ctrl + /
// 多行注释：shift + alt + a


// TODO: JS结束符
// 作用：使用英文;代表结束符
// 实际开发过程中，可写可不写，浏览器（JS引擎）可以自动推断语句的结束位置
// 约束：在开发过程中，为了风格统一，结束符要么每句都写，要么每句都不写（统一规范）


// TODO: 输入输出语法
// 1. 文档输出：将内容输出到浏览器页面中（body标签中）
// 注意：文档输出中可以直接输出文字，也可以输出带有标签（标签会被浏览器渲染处理）的文字到页面中
document.write("文档输出（文字输出）");
document.write("<h1>文档输出（带有标签输出）</h1>");
// 2. alert("")：页面弹出警告对话框
alert("警告弹框");
// 3. 控制台输出：将输出内容打印到浏览器控制台中
console.log("控制台输出");
// 4. 输入
prompt("请输入你的年龄");


// TODO:变量的基本使用
// 语法（声明变量）：let 变量名 
// 变量初始化： 变量名 = 值
let age
age = 20
// 变量声明并初始化
let name = "张三"
// 同时声明多个变量（声明时必须给所有变量初始化）
let userName = "李四", password = "123456"
// 注意：变量在使用之前，必须先进行赋值操作



// TODO:标识符命名规则：
// 规则：
//      a. 不能使用关键字
//      b. 只能使用_、字母、数字、$，不能使用数字开头
//      c. 严格区分大小写
// 规范：
//      a. 见名知意
//      b. 遵循“小驼峰”命名法


// TODO:数组使用
// 注意：数组可以存放任意类型的数据（元素类型可以不同，类似java中的集合）
// 声明数组：let 数组名 = [数据1,数据2,.....]
// 如：
let array = [10, 20, "张三", 40, "李四"]
// 获取数组中的长度（元素个数）：数组名.length
let len = array.length;


// TODO:常量
// 常量声明：const 常量名 = 值
// 注意：
//      a. 常量在声明的同时，必须要进行初始化赋值操作
//      b. 常量初始化后，其值不能再被修改了
//      c. 常量命名通常全大写，多个单词使用-隔开
// 如：
const PI = 3.14
// 注意: 
// 引用数据类型,变量保存的是地址
// 所以使用: const array = ["张三"]
// array.push(10);是合法的
// 原因: 数组是引用数据类型,向数组中添加元素,其地址没有被改变



// TODO:数据类型
// 1. 基本数据类型：
//      a. number（数字型）、string（字符串类型）
//      b. boolean（布尔型）、undefined（未定义型）、null（空类型）
// 2. 引用数据类型：
//      a. object
// 注意：
//      a. 数字型包括：整数、小数
//      b. 变量在声明且没有赋值的情况下，默认：undefined
// null与nudefined的区别：
//      a. undefined：表示没有赋值
//      b. null：赋值了，但是内容为空
// null的使用场景：
// 把null作为尚未创建的对象
// 理解：就是将有个变量里面存放的是一个对象，但是对象还没创建好，此时
// 可以先给变量赋值为null
// 注意：
// Number.MAX_VALUE：获取Number所能接收的最大值
// 当接收的数字，远大于Number所能接收的最大值，则会返回Infinity（无穷大）
//如：
let num = Number.MAX_VALUE * 1000;
// Infinity是字面量表示正无穷（它也是number类型），可以直接使用的
let num02 = Infinity;
// NaN：表示“不是数字”，字面量（也是number类型），也是可以直接使用的
// 比如：
let num03 = parseInt("a123");   // 会返回NaN，表示不是数字
let num04 = NaN;        // 也可以直接赋值给变量使用


// TODO: 转boolean类型
// 使用方法：Boolean(数据)
// 数字除0和NaN外,其他任意数字都为true
// 字符串类型,除''为false,其他所有字符串都为true
// undefined转boolean,始终为false
// null转boolean,始终为false



// TODO:类型转换
// 隐式转换
// 规则：
//      a. +号两边只有一个是字符串，则会将另一个转换为字符串
//      b. 除+号之外的“算术运算符”外，比如：-、*都会把数据转为数字类型
// 缺点：转换类型不明确，靠经验说话
// 小技巧：+号作为正号，可以其他类型转为数字类型
// 如：
let a = 10 + "年了";      // 字符串拼接了，将10转为字符串类型，再拼接
let b = 10 - '2';        // 运算符（+除外），转为数字类型，再运算，结果：8
let c = +'123';         // +号当作“正号”使用，可以其他类型转为数字类型

// TODO: 显示转Number
// Number(数据)：转换为数字类型
// 注意：如果字符串内容里面有“非数字”，会转换失败，结果：NaN（表示不是一个数字）
// NaN也是number类型的数据，代表“非数字”
// parseInt(数据)：转数字类型
// 注意：只保留整数
// parseFloat(数据)：转数字类型
// 注意：可以保留小数
// 如：
let parseIntResult = parseInt('12px');  // 结果：12，只保留整数部分
let parseFloatResult = parseFloat('12.34px');   // 结果：12.34
// 注意：parseInt、parseFloat是逐字符遍历的，当遇到不是数字的就会停止
// 所以使用parseInt或parseFloat时，如果第一个字符是“非数字”，则会直接停止
// 如：
let num05 = parseInt("a123");       // NaN
// parseInt与parseFloat要转的数据，如果是非字符串类型的，parseInt会将其转
// 为字符串，再进行转Number操作
// 注意:
// 利用-,转换为数字类型时,其字符串内容必须是数字
// 利用-隐式转换数字类型时,如果是'',则会被转换为0
// null经过数字转变后,变成0
// undefined经过数字转换后,变成NaN



// TODO：数据类型转换String
// 方式一：
//      调用被转换数据类型的toString();
// 如：
let num06 = 123;
let str02 = num06.toString();
console.log(num06);         // number
console.log(str02);         // string
// 注意：toString()不会改变 “原本的数据” 类型，只是复制一份原本数据，进行转换
// 再将转换后的结果以字符串的类型返回
// 注意：“null与undefined两种类型是没有toString()方法的”
// 方式二：
//      调用String()函数
//      let num = String(数据);
let num07 = String(123);
// 方法三：字符串拼接


// TODO:检查数据类型（typeof）
// typeof：可以检测变量的数据类型
// 语法：
//      a. 作为运算符：typeof 变量名/字面量
//      b. 函数形式：typeof(变量名/字面量)
// 常用 “运算符” 形式的
let pwd = "123456";
console.log(typeof pwd);
console.log(typeof (pwd));


// TODO:算术运算符
// +、-、*、/、%
// 优先级（括号内同级）：(*、/、%) > (+、-)


// TODO:赋值运算符
// =、+=、-=、*=、/=、%=


// TODO: 一元运算符
// 自增：++
// 自减：--
// 前置自增：先加一，再进行运算
// 后置自增：先运算，再加一
// 自减与自增用法一致
// 如：
let i = 1;
i++;        // 后置自增


// TODO:比较运算符
// >、<、>=、<=、==、===、!==、
// ==：相等，左右两边是否相等（值是否相等）
// ===：全等，左右两边是否 “类型和值” 都相等
// !==：不全等，左右两边是否不全等 
// 注意：
// ==：先将其他类型的值“隐式转换”为数字类型，再进行比较其值是否相等
console.log(2 == '2');      // true，比较的是值，没有比较类型
// 字符串比较（比较的是字符的ASII码值）


// TODO:逻辑运算符
// &&、||、!


// TODO: 运算符的优先级
// 优先级从上倒下依次降低
/**
 * ()：优先级最高
 * ++、--、!：一元运算符
 * 先*、/、%，后+、-：算术运算符
 * >、>=、<、<=：关系运算符
 * ==、!=、===、!==：相等运算符
 * 先&&，后||：逻辑运算符
 * =：赋值运算符
 * ,：逗号运算符
 * 
 */


// 三元运算符
// 语法：表达式 ? 满足条件后的表达式 : 不满足条件后的表达式
// 如：
let item = true ? 10 : 20;

// TODO:字符串类型
// 字符串：使用单引号、双引号、或反引号（``）包裹的数据
// 单引号双引号，没有区别，推荐使用单引号
// 注意：同种引号不能嵌套，如想要字符串内保留引号，可以使用“转义字符”或采用“外单内双、外双内单”的写法
// 如：
let str = "你好，''"        // 外双内单
str = '你好,""'            //  外单内双
str = '你好,\"\"'          //  转义字符


// TODO:模板字符串
let myAge = 18;
console.log(`大家好，我今年${age}岁了`);



// TODO: 单分支语句
// if(条件){}


//TODO: 双分支语句
// if(条件){}
// else {}


// TODO: 多分支语句
// if(条件){}
// else if(条件) {}
// ....
// else {}


// TODO: switch分支语句
// 语法：
/**
 * switch(表达式) {
 *  case 值1:
 *      代码1
 *      break
 *  case 值2:
 *      代码2
 *      break
 *  ....
 *  default:
 *      代码n
 *      break
 * }
 * 注意：表达式的值与值进行“全等匹配”
 * case，一般配合break使用，否则会出现case穿透问题
 */


// TODO:循环语句
// while(循环条件) {}
// for(初始化语句;判断语句;条件修改语句;){}
// continue：跳过当前循环
// break：退出当前循环


// TODO: 数组
// 数组是一种数据类型(对象类型)
// 声明数组(字面量声明方式):let 数组名 = [数据,数据,....]
// 声明数组(new Array()): let 数组名 = new Array(数据,数据.....);
// 注意:数组是可以存放"任意类型"的数据的(元素类型可以不同)
// 如:
let arrayTest = ["张三", 1, 2, true];
// 注意：
let array02 = [10];                 // 创建数组，长度为1，元素为10
let array03 = new Array(10);        // 创建长度为10的数组
// 当创建数组时，只有一个整数元素时，需要使用"字面量"的方式创建
// new Array()方式会将传入的一个元素，当作数组的长度参数处理
// 添加数组元素
// 数组名.push(数据):将一个或多个元素添加到数组的"末尾",并返回该数组的新长度
let myArray = [10, 20, 30];
let arraylen = myArray.push(10, 20, '张三');
// 数组名.unshift(数据):将一个或多个元素添加到数组的"开头",并返回数组的长度
let myArray_02 = [10, 20];
let arrayLen = myArray_02.unshift(40, 50, '李四');
// 删除数组元素
// 数组名.pop():从数组中"末尾"删除一个元素,并返回该元素的值
let element = myArray.pop();
// 数组名.shift():从数组中"开头"删除一个元素,并返回该元素的值
let element02 = myArray_02.shift();
// 数组名.splice(start,deleteCount):删除指定元素
// start:起始位置(索引)
// deleteCount:删除的元素个数
array.splice(1, 2);
// 注意: start不写,默认是:0,即:数组第一个元素开始
// deleteCount不写,默认删除到最后(从起始位置开始删除所有元素)
// 所以当两个参数都不写时,则删除数组中的所有元素

// 通过修改数组的长度，删除数组元素
array.length = 1;

// 数组截取
// array.slice(start, end);
// start：起始位置索引
// end：结束位置索引
// 不会修改原数组，会返回一个新数组

// TODO：forEach遍历数组
array.forEach((value, index, obj) => {
    // value：数组的元素
    // index：数组的下标
    // obj：当前遍历的数组的对象，即：array
});


// TODO：定时器
// let res = setInterval(回调函数,间隔时间);
// setInterval()返回值：number，计时器编号（唯一标识）
// 可以通过唯一标识，关闭对应的定时器
// clearInterval(计时器编号)：关闭指定的计时器
// 注意：定时器是异步的

// TODO：延时调用
// 一个函数不马上执行，而是隔一定的时间才开始执行（只执行一次）
// let time = setTimeout(回调函数,间隔时间);
// 返回值：number，延时调用唯一标识
// clearInterval(唯一编号)：关闭延时调用
// 注意：延时调用是异步的


// TODO: JS执行机制
// https://www.bilibili.com/video/BV1Y84y1L7Nn?p=128&vd_source=c56ed2f67ce751cb9fe7981487f1d5f1



// TODO：数组解构
// 将数组的单元值快速批量赋值给一系列变量的简洁写法
// 语法：
const array04 = [10, 20, 30]
// num08 = 10   num09 = 20  num10 = 30
const [num08, num09, num10] = array04;
// 注意：解构赋值是依次对应位置赋值的，如：第一个位置的变量赋值第一个元素值

// 数组解构例题：交换两个变量
let num11 = 10;
let num12 = 20;
[num11, num12] = [num12, num11];
// 变量少，单元值多，可以使用”剩余参数“接收
// 使用...arr接收多余的值
[num11, num12, ...arr] = [1, 2, 3, 4];
// 数组解构时，变量也可以赋值默认值
[num11 = 0, num12 = 0] = [1, 2];
// 作用：避免undefined传递
// 如：传的是一个空数组
[num11 = 0, num12 = 0] = [];
// 按需导入赋值
// a = 1    b = 2   d = 4
[a, b, , d] = [1, 2, 3, 4];
// 多维数组解构
let [aa, bb, [cc, dd]] = [1, 2, [3, 4]]


// TODO：对象解构
const obj = {
    uname: "张三",
    myage: 18
}
// 解构对象时，变量的名称与对象属性名相同
const { uname, myage } = obj;
// 等价于
//  const uname = obj.uname;
//  const myage = obj.myage; 
// 注意：我们可以看出解构赋值其实也是声明了变量的
// 所以我们要注意声明变量时，变量名不能冲突
// 如：我们已经声明了一个 uname变量
// 那解构赋值时，就不能使用uname作为变量，再重新声明定义了
// 如：
// const pass = 10;
// const testObj = { pass: 20 }
// const { pass } = testObj;

// 对象解构的变量名，可以重新改名
// 语法：旧变量名:新变量名
const obj02 = {
    name: "张三",
    age: 18
}
const { name: username, age: userAge } = obj02;
// 等价于
// const username = obj02.name;
// const userAge = obj02.age;

// 多级对象也可以使用解构



// TODO：异常处理
// 抛出异常：
//          throw new Error("异常信息");
//          会中止程序
// 捕获异常：
//          try {可能发生错误的代码}
//          catch(err) { 错误处理代码 }
//          finally { 不管程序对不对，都会在执行的代码 }
